using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using GraphMyCode.Web.Dtos;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace GraphMyCode.Web
{
	public class AssemblyReader
	{
		internal static GraphMapDto GetMethodGraph(byte[] assemblyBytes)
		{
			AssemblyDefinition assembly = AssemblyFactory.GetAssembly(assemblyBytes);
			var colorService = new ColorService();

			Dictionary<MethodReference, VertexDto> verticies = new Dictionary<MethodReference, VertexDto>();

			int id = 0;
			//List<LegendItem> legendItems = new List<LegendItem>();

			var graphModel = new GraphDto();
			foreach (TypeDefinition type in assembly.MainModule.Types)
			{
				foreach (MethodDefinition method in type.Methods.OfType<MethodDefinition>().Where(m => m.HasBody && !m.IsGetter && !m.IsSetter))
				{
					string color = colorService.GetColorByObject(type);

					//var lItem = legendItems.FirstOrDefault(i => i.Color == color);
					//if (lItem == null)
					//    legendItems.Add(new LegendItem { Color = color, Shape = NodeShape.Ellipse, Text = type.Name });

					var methodAreal = string.Format("{0}\n({1})", method.Name,
													string.Join(",",
																method.Parameters.OfType<ParameterDefinition>().Select(
																	pd => pd.ParameterType.Name).
																	ToArray()));

					var v = new VertexDto
					{
						Color = color,
						Id = id++,
						Shape = NodeShape.Ellipse,
						Text = methodAreal,
						Weight = (double)method.Body.CodeSize / 100,
						Type = "Method"
					};
					v.Attributes.Add("Code Size", method.Body.CodeSize.ToString());
					v.Attributes.Add("Assembly", type.Module.Assembly.Name.Name);
					v.Attributes.Add("Type", type.Name);
					v.Attributes.Add("Modifier", method.IsPublic ? "public" : method.IsPrivate ? "private" : "protected");

					graphModel.AddVertex(v);
					verticies.Add(method, v);
				}
			}

			foreach (var pair in verticies)
			{
				MethodDefinition method = pair.Key as MethodDefinition;
				if (method != null)
					foreach (Instruction instruction in method.Body.Instructions.OfType<Instruction>().
							Where(i => i.OpCode == OpCodes.Call ||
									   i.OpCode == OpCodes.Calli ||
									   i.OpCode == OpCodes.Callvirt))
					{
						MethodReference refMethod = instruction.Operand as MethodReference;
						if (refMethod != null && refMethod != method)
						{
							if (verticies.ContainsKey(refMethod))
							{
								var edge = graphModel.Edges.FirstOrDefault(e => e.Target == verticies[refMethod]);
								if (edge != null)
									edge.Thick += 1;
								else
								{
									graphModel.AddEdge(new EdgeDto
									{
										IsDirectional = true,
										Thick = 1,
										Source = verticies[method],
										Target = verticies[refMethod]
									});
								}
							}
						}
					}
			}

			GraphMapDto graphMapDto = new GraphMapDto
										{
											Graph = graphModel,
											//Legend = CreateLegend(legendItems),
											Metadata =
												new MetadataDto
													{
														Title = assembly.Name.Name,
														Description = "Methods",
														Type = ExportType.Method
													}
										};

			return graphMapDto;
		}

		internal static GraphMapDto GetClassGraph(byte[] assemblyBytes)
		{
			AssemblyDefinition assembly = AssemblyFactory.GetAssembly(assemblyBytes);

			ColorService colorService = new ColorService();

			var graphModel = new GraphDto();

			Dictionary<TypeReference, VertexDto> verticies = new Dictionary<TypeReference, VertexDto>();
			//List<LegendItem> legendItems = new List<LegendItem>();

			int id = 0;
			foreach (TypeDefinition type in assembly.MainModule.Types)
			{
				string color = colorService.GetColorByObject(type);

				//var lItem = legendItems.FirstOrDefault(i => i.Color == color);
				//if (lItem == null)
				//    legendItems.Add(new LegendItem
				//    {
				//        Color = color,
				//        Shape = NodeShape.Box,
				//        Text = type.Name
				//    });

				var v = new VertexDto
				{
					Color = color,
					Id = id++,
					Shape = NodeShape.Ellipse,
					Text = type.Name,
					Weight = type.Methods.Count + type.Properties.Count + type.Events.Count,
					Type = type.Name
				};

				v.Attributes.Add("Assembly", type.Module.Assembly.Name.Name);
				v.Attributes.Add("Methods", type.Methods.Count.ToString(CultureInfo.CurrentCulture));
				v.Attributes.Add("Properties", type.Properties.Count.ToString(CultureInfo.CurrentCulture));
				v.Attributes.Add("Fields", type.Fields.Count.ToString(CultureInfo.CurrentCulture));
				v.Attributes.Add("Events", type.Events.Count.ToString(CultureInfo.CurrentCulture));												

				graphModel.AddVertex(v);
				verticies.Add(type, v);
			}

			foreach (var pair in verticies)
			{
				TypeDefinition type = (TypeDefinition)pair.Key;

				foreach (MethodDefinition method in type.Methods.OfType<MethodDefinition>().Where(m => m.HasBody))
				{
					foreach (Instruction instruction in method.Body.Instructions.OfType<Instruction>().
						Where(i => i.OpCode == OpCodes.Call ||
								   i.OpCode == OpCodes.Calli ||
								   i.OpCode == OpCodes.Callvirt))
					{
						MethodReference refMethod = instruction.Operand as MethodReference;
						if (refMethod != null && method.DeclaringType != refMethod.DeclaringType)
						{
							if (verticies.ContainsKey(refMethod.DeclaringType))
							{
								var edge =
									graphModel.Edges.FirstOrDefault(
										e => e.Target.Text == refMethod.DeclaringType.Name || e.Source.Text == refMethod.DeclaringType.Name);
								if (edge != null)
									edge.Thick += 1;
								else
								{
									graphModel.AddEdge(new EdgeDto
									{
										IsDirectional = false,
										Thick = 1,
										Source = verticies[method.DeclaringType],
										Target = verticies[refMethod.DeclaringType]
									});
								}
							}
						}
					}
				}
			}

			GraphMapDto graphMapDto = new GraphMapDto
										{
											Graph = graphModel,
											//Legend = CreateLegend(legendItems),
											Metadata =
												new MetadataDto
													{
														Title = assembly.Name.Name,
														Description = "Classes",
														Type = ExportType.Class
													}
										};

			return graphMapDto;
		}

		internal static GraphMapDto GetAssemblyGraph(byte[] assemblyBytes)
		{
			AssemblyDefinition assembly = AssemblyFactory.GetAssembly(assemblyBytes);
			var colorService = new ColorService();

			var graphModel = new GraphDto();

			Dictionary<AssemblyNameReference, VertexDto> verticies = new Dictionary<AssemblyNameReference, VertexDto>();
			//List<LegendItem> legendItems = new List<LegendItem>();

			int id = 0;
			string color = colorService.GetColorByObject(assembly);

			var v = new VertexDto
			{
				Color = color,
				Id = id++,
				Shape = NodeShape.Ellipse,
				Text = assembly.Name.Name,
				Weight = 10,
				Type = "Assembly"
			};
			v.Attributes.Add("Version", "Assembly");
			v.Attributes.Add("Weight", v.Weight.ToString(CultureInfo.InvariantCulture));
			v.Attributes.Add("ExportType", "Class");

			graphModel.AddVertex(v);
			verticies.Add(assembly.Name, v);


			foreach (AssemblyNameReference asm in assembly.MainModule.AssemblyReferences)
			{
				color = colorService.GetColorByObject(asm);
				
				//var lItem = legendItems.FirstOrDefault(i => i.Color == color);
				//if (lItem == null)
				//    legendItems.Add(new LegendItem
				//    {
				//        Color = color,
				//        Shape = NodeShape.Ellipse,
				//        Text = asm.Name
				//    });

				v = new VertexDto
				{
					Color = color,
					Id = id++,
					Shape = NodeShape.Ellipse,
					Text = asm.Name,
					Weight = 10,
					Type = "Assembly"
				};

				v.Attributes.Add("Version", asm.Version.ToString());				

				graphModel.AddVertex(v);
				verticies.Add(asm, v);
			}

			foreach (TypeDefinition type in assembly.MainModule.Types)
			{
				foreach (
					MethodDefinition method in
						type.Methods.OfType<MethodDefinition>().Where(m => m.HasBody && !m.IsGetter && !m.IsSetter))
				{
					foreach (Instruction instruction in method.Body.Instructions.OfType<Instruction>().
							Where(i => i.OpCode == OpCodes.Call ||
									   i.OpCode == OpCodes.Calli ||
									   i.OpCode == OpCodes.Callvirt))
					{
						MethodReference refMethod = instruction.Operand as MethodReference;
						if (refMethod != null)
						{
							AssemblyNameReference asm = refMethod.DeclaringType.Scope as AssemblyNameReference;

							if (asm != null && asm != assembly.Name && verticies.ContainsKey(asm))
							{
								var edge = graphModel.Edges.FirstOrDefault(e => e.Target.Text == asm.Name);
								if (edge != null)
									edge.Thick += 1;
								else
								{
									graphModel.AddEdge(new EdgeDto
									{
										IsDirectional = false,
										Thick = 1,
										Source = verticies[assembly.Name],
										Target = verticies[asm]
									});
								}
							}
						}
					}
				}
			}

			GraphMapDto graphMapDto = new GraphMapDto
										{
											Graph = graphModel,
											//Legend = CreateLegend(legendItems),
											Metadata =
												new MetadataDto
													{
														Title = assembly.Name.Name,
														Description = "Assemblies",
														Type = ExportType.Assembly
													}
										};

			return graphMapDto;
		}


		private static LegendDto CreateLegend(List<LegendItem> legendItems)
		{
			return new LegendDto
			{
				Items = legendItems.ToArray(),
				IsVisible = true,
				ItemSize = 1,
				BackgroundColor = "Wheat",
				BackgroundOpacity = .5,
				BorderColor = "Black",
				ItemMargin = 1
			};
		}
	}
}